/*-*-C-*-
 * Grid dialogue box for Window CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dbox.h"
#include "interactor.h"
#include "registry.h"

#include "format.h"
#include "windowedit.h"
#include "colours.h"
#include "grid.h"
#include "gui.h"
#include "icondefs.h"
#include "protocol.h"


static WindowPtr gridwin = NULL;
static Bool asmenu = FALSE;


/*
 * Load templates, etc.
 */

error * grid_load_prototypes ()
{
    ER ( wimp_load_template("Grid", &gridwin) );
    ER ( swi (Wimp_CreateWindow,  R1, &gridwin->visarea,
                            OUT,  R0, &gridwin->handle,  END) );

    /* must be registered for interactive help */
    return registry_register_window (gridwin->handle,
                                       GridDbox, (void *) gridwin);
}


/*
 * Called from windowedit_load(..) to initialise the window object's grid
 */

void grid_set_defaults (GridPtr grid)
{
    grid->horizdiv = atoi (message_lookup (&msgs, "8HorizGrid"));
    grid->vertdiv = atoi (message_lookup (&msgs, "8VertGrid"));
    grid->show = strcmp (message_lookup (&msgs, "ShowGrid"), "on") == 0; 
    grid->lock = strcmp (message_lookup (&msgs, "LockGrid"), "on") == 0;

    return;
}


/*
 * Returns the value of 'x' rounded to the nearest multiple of 'grid'.
 */

static int snap_int (int x, int grid)
{
    int a, b;

    if (x >= 0)
    {
        a = x % grid;
        b = grid - a;
        if (a < b)
            x -= a;
        else
            x += b;
    }
    else
    {
        a = (-x) % grid;
        b = grid - a;
        if (a <= b)
            x += a;
        else
            x -= b;
    }

    return x;
}


/*
 * Updates 'point' to be the nearest grid point in 'window'.
 */

void grid_snap_point (WindowObjPtr window, PointPtr point)
{
    point->x = snap_int (point->x, window->grid.horizdiv);
    point->y = snap_int (point->y, window->grid.vertdiv);

    return;
}


/*
 * Called to initialise or reset the fields of the grid dialogue box.
 */

static error * grid_init_dbox (WindowObjPtr window)
{
    dbox_setint (gridwin, I_GRID_HORIZONTAL, window->grid.horizdiv);
    dbox_setint (gridwin, I_GRID_VERTICAL, window->grid.vertdiv);
    dbox_setbutton (gridwin, I_GRID_SHOW, window->grid.show);
    dbox_setbutton (gridwin, I_GRID_LOCK, window->grid.lock);

    return NULL;
}


/*
 * Called when grid dialogue is complete
 */

static error * grid_apply_dbox (WindowObjPtr window)
{
    Bool prevshow = window->grid.show;
    int prevhorizdiv = window->grid.horizdiv;
    int prevvertdiv = window->grid.vertdiv;

    window->grid.horizdiv = WIMP_ALIGN_COORD (
                                dbox_getint (gridwin, I_GRID_HORIZONTAL) );
    if (window->grid.horizdiv == 0)
        window->grid.horizdiv = 4;
    window->grid.vertdiv = WIMP_ALIGN_COORD (
                                dbox_getint (gridwin, I_GRID_VERTICAL) );
    if (window->grid.vertdiv == 0)
        window->grid.vertdiv = 4;
    window->grid.show = dbox_getbutton (gridwin, I_GRID_SHOW);
    window->grid.lock = dbox_getbutton (gridwin, I_GRID_LOCK);

    /* force redraw if necessary */
    if (prevshow != window->grid.show ||
        (window->grid.show &&
         (prevhorizdiv != window->grid.horizdiv ||
          prevvertdiv != window->grid.vertdiv)))
    {
        RectRec invalid;

        wimp_convert_rect (ScreenToWork, window->window,
                                         &window->window->visarea, &invalid);
        wimp_invalidate (window->window, &invalid);
    }

    return NULL;
}


/*
 * Interactor for the grid window.  Expects to be pushed on top of the
 * menu.c interactor, so needs to be careful about popping itself correctly.
 */

static error * grid_interactor (unsigned int event, int *buf, void *closure, Bool *consumed)
{
    MouseClickPtr mouse = (MouseClickPtr) buf;
    WindowPtr win = (WindowPtr) buf;         /* only half there */
    KeyPressPtr key = (KeyPressPtr) buf;
    WindowObjPtr window = (WindowObjPtr) closure;
    error *err = NULL;

    if (buf == NULL)                     /* we are being asked to cancel */
        return NULL;

    switch (event)
    {
    case EV_OPEN_WINDOW_REQUEST:
        if (win->handle == gridwin->handle)
        {
            *consumed = TRUE;
            gridwin->visarea = win->visarea;
            gridwin->scrolloffset = win->scrolloffset;
            gridwin->behind = win->behind;
            ER ( swi (Wimp_OpenWindow, R1, gridwin, END) );
        }
        break;

    case EV_KEY_PRESSED:
        if (key->caret.windowhandle == gridwin->handle)
        {
            unsigned int modifiers = wimp_read_modifiers ();

            *consumed = TRUE;
            switch (key->code)
            {
            case 0x0d:   /* RETURN */
                err = grid_apply_dbox (window);
                if (err)
                {
                    error_box (err);
                    break;
                }

            case 0x1b:   /* ESCAPE */
                if ((modifiers & MODIFIER_SHIFT) == 0)
                {
                    if (asmenu)
                        swi (Wimp_CreateMenu, R1, -1, END);
                    /* return input focus to parent window */
                    dbox_set_caret_to (window->window, -1);
                    interactor_cancel();
                }
                else
                    grid_init_dbox (window);
                break;

            default:
                *consumed = FALSE;
                break;
            }
        }
        break;

    case EV_MOUSE_CLICK:
        if (mouse->windowhandle == gridwin->handle)
        {
            unsigned int modifiers = wimp_read_modifiers ();
            int buttons = mouse->buttons;
            int icon = mouse->iconhandle;
            Bool adjustclick = buttons == MB_CLICK(MB_ADJUST);
            Bool selectclick = buttons == MB_CLICK(MB_SELECT);
            Bool menuclick = buttons == MB_CLICK(MB_MENU);
            int dir = adjustclick ? -8 : 8;

            *consumed = TRUE;
            switch (icon)
            {
            case I_GRID_OK:
                if (selectclick || adjustclick)
                {
                    err = grid_apply_dbox (window);
                    if (err)
                    {
                        error_box (err);
                        break;
                    }
                }
            case I_GRID_CANCELT:
                if (selectclick)
                {
                    if (asmenu)
                        swi (Wimp_CreateMenu, R1, -1, END);
                    /* return input focus to parent window */
                    dbox_set_caret_to (window->window, -1);
                    interactor_cancel ();
                }
                else if (adjustclick)
                    grid_init_dbox (window);
                break;

            case I_GRID_VERTICAL_ADJ_DOWN:
                dir = -dir;
            case I_GRID_VERTICAL_ADJ_UP:
                if (!menuclick)
                    gui_adjust_len (gridwin, I_GRID_VERTICAL, -1,
                                    dir, modifiers);
                break;

            case I_GRID_HORIZONTAL_ADJ_DOWN:
                dir = -dir;
            case I_GRID_HORIZONTAL_ADJ_UP:
                if (!menuclick)
                    gui_adjust_len (gridwin, I_GRID_HORIZONTAL, -1,
                                    dir, modifiers);
                break;
            }
        }
        break;
    }

    return NULL;
}


/*
 * Open the grid dialogue as a submenu at the appointed position
 *
 * If position is NULL, then the dbox is to be opened as a transient dbox in
 *  its default position.
 *
 * If position is not NULL, then the dbox is to be opened as a submenu at the
 *  specified position.
 */

error * grid_open (WindowObjPtr window, PointPtr position)
{
    grid_init_dbox (window);

    if (position == NULL)
    {
        asmenu = TRUE;

        ER ( swi (Wimp_CreateMenu, R1, gridwin->handle,
                                   R2, gridwin->visarea.minx,
                                   R3, gridwin->visarea.maxy, END) );
        interactor_install (grid_interactor, (void *) window);

        /* give input focus to the dbox */
        ER ( dbox_set_caret_to (gridwin, -1) );
    }
    else
    {
        asmenu = FALSE;

        /* record new position of window in case opened by key next time */
        gridwin->visarea.maxx = position->x +
                         (gridwin->visarea.maxx - gridwin->visarea.minx);
        gridwin->visarea.minx = position->x;
        gridwin->visarea.miny = position->y -
                         (gridwin->visarea.maxy - gridwin->visarea.miny);
        gridwin->visarea.maxy = position->y;

        ER ( swi (Wimp_CreateSubMenu,  R1, gridwin->handle,
                                       R2, position->x,
                                       R3, position->y,  END) );

        /* give input focus to the dbox */
        ER ( dbox_set_caret_to (gridwin, -1) );
        interactor_push (grid_interactor, (void *) window);
    }

    return NULL;
}


/*
 * Called during the redraw loop to display the grid.
 */

void grid_show (WindowObjPtr window, RectPtr work)
{
    int gridx = window->grid.horizdiv;
    int gridy = window->grid.vertdiv;
    PointRec start, stop;
    int offset, x, y;
    int colour;

    /* choose suitable colour according to window's background colour */
    colour = uncolour [window->window->colours.workBG];
    swi (Wimp_SetColour, R0, colour, END);

    /* establish limits for grid lattice */
    offset = work->minx % gridx;
    if (offset < 0)
        offset += gridx;
    start.x = work->minx;
    if (offset != 0)
        start.x += (gridx - offset);
    offset = work->maxx % gridx;
    if (offset < 0)
        offset += gridx;
    stop.x = work->maxx;
    if (offset != 0)
        stop.x += (gridx - offset);
    offset = work->miny % gridy;
    if (offset < 0)
        offset += gridy;
    start.y = work->miny;
    if (offset != 0)
        start.y += (gridy - offset);
    offset = work->maxy % gridy;
    if (offset < 0)
        offset += gridy;
    stop.y = work->maxy;
    if (offset != 0)
        stop.y += (gridy - offset);

    /* and convert to screen coordinates */
    wimp_convert_point (WorkToScreen, window->window, &start, &start);
    wimp_convert_point (WorkToScreen, window->window, &stop, &stop);

    /* plot the grid lattice */
    for (x = start.x; x <= stop.x; x += gridx)
        for (y = start.y; y <= stop.y; y += gridy)
            swi (OS_Plot, R0, 5 + 64, R1, x, R2, y, END);

    return;
}


/*
 * Determines the alignment point for 'gadget', and then works out how
 *  far the gadget must be moved to make this as close as possible to the
 *  nearest grid point - subject to the gadget's position (top left hand
 *  corner) remaining at a multiple of 4 OS units.
 */

static void snap_gadget (
    GadgetPtr gadget,
    PointPtr delta
)
{
    int halign, valign;
    PointRec alignpos;

    /* find out about gadget's alignment */
    halign = (int) gadget->def->align.horizontal;
    valign = (int) gadget->def->align.vertical;

    /* handle special case of dynamic alignment position */
    if (halign == (int) ALIGNPOS_SPECIAL)
    {
        union { error * (*f) (GadgetPtr, int *, int *); int i; } x;

        /* how to call the function in 'valign' without a compiler warning */
        x.i = valign;
        x.f (gadget, &halign, &valign);
    }

    /* determine position of alignment point */
    switch (valign)
    {
    case (int) ALIGNPOS_LEFT:
        alignpos.x = gadget->hdr.bbox.minx;
        break;
    case (int) ALIGNPOS_RIGHT:
        alignpos.x = gadget->hdr.bbox.maxx;
        break;
    case (int) ALIGNPOS_CENTRE:
        alignpos.x = (gadget->hdr.bbox.minx + gadget->hdr.bbox.maxx)/2;
        break;
    }

    switch (halign)
    {
    case (int) ALIGNPOS_TOP:
        alignpos.y = gadget->hdr.bbox.maxy;
        break;
    case (int) ALIGNPOS_BOTTOM:
        alignpos.y = gadget->hdr.bbox.miny;
        break;
    case (int) ALIGNPOS_CENTRE:
        alignpos.y = (gadget->hdr.bbox.miny + gadget->hdr.bbox.maxy)/2;
        break;
    }

    /* determine how far we must move to the nearest grid point */
    delta->x = alignpos.x;
    delta->y = alignpos.y;
    grid_snap_point (gadget->owner, delta);
    delta->x -= alignpos.x;
    delta->y -= alignpos.y;

    /* and finally ensure that this move maintains alignment to 4 OS units */
    wimp_align_point (delta);

    return;
}


/*
 * Called to "snap" the selected gadgets to the grid.
 */

error * grid_snap (WindowObjPtr window)
{
    GadgetPtr gadget = window->gadgets;
    RectRec bbox;
    Bool modified = FALSE;

    bbox.minx = bbox.miny = BIG;
    bbox.maxx = bbox.maxy = -BIG;

    /* process each selected gadget in turn */
    while (gadget != NULL)
    {
        if (gadget->selected)
        {
            PointRec delta;

            /* determine how gadget must be moved to snap to grid */
            snap_gadget (gadget, &delta);

            /* must invalidate if gadget has moved */
            if (delta.x != 0 || delta.y != 0)
            {
                modified = TRUE;

                wimp_merge_bboxes (&bbox, &bbox, &gadget->hdr.bbox);
                gadget->hdr.bbox.minx += delta.x;
                gadget->hdr.bbox.maxx += delta.x;
                gadget->hdr.bbox.miny += delta.y;
                gadget->hdr.bbox.maxy += delta.y;
                wimp_merge_bboxes (&bbox, &bbox, &gadget->hdr.bbox);
            }
        }

        gadget = gadget->next;
    }

    if (modified)
    {
        /* note modification */
        protocol_send_resed_object_modified (window);

        /* invalidate rectangle that includes original and moved gadgets */
        windowedit_add_ears_to_bbox (&bbox, &bbox);
        wimp_invalidate (window->window, &bbox);
    }

    return NULL;
}
